package cn.ijero.linechart

import android.graphics.DashPathEffect
import android.graphics.Paint
import android.graphics.Path
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress
import androidx.compose.runtime.*
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
import androidx.compose.ui.graphics.nativeCanvas
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import cn.ijero.linechart.ext.toNativeColor
import cn.ijero.linechart.ui.theme.*
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.roundToInt

val AxisLineColor = Color(0xFF979797)

val DashedLineColor = Color(0xFFE3E5EC)
val CaptionTextColor = Color(0xFF999999)

val RedColor = Color(0xFFE05046)
val GreenColor = Color(0xFF1DB270)
val ZeroBgColor = Color(0xFFD1D1D1)

@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun <T : ILine> LineChart(
    modifier: Modifier = Modifier,
    data: LineChartData<T>,
    style: LineChartStyle
) {
    if (data.lines.isEmpty()) return
    var max by remember {
        mutableStateOf(Double.MIN_VALUE)
    }
    var min by remember {
        mutableStateOf(Double.MAX_VALUE)
    }
    LaunchedEffect(key1 = data) {
        // temp variable
        val tMax = max(
            abs(data.lines.maxOf { it -> it.second.maxOfOrNull { it.value() } ?: 0.0 }),
            abs(data.lines.minOf { it -> it.second.minOfOrNull { it.value() } ?: 0.0 })
        )
        max = tMax
        min = -tMax
    }

    var eventX by remember {
        mutableStateOf(-1f)
    }
    val timeText = data.lines.getOrNull(0)?.second?.getOrNull(0)?.dateTime() ?: "########"
    Canvas(
        modifier = modifier
            .pointerInput(Unit) {
                detectDragGesturesAfterLongPress(
                    onDragCancel = {
                        eventX = -1f
                    },
                    onDragEnd = {
                        eventX = -1f
                    },
                    onDragStart = {
                        eventX = it.x
                    }
                ) { change, dragAmount ->
                    eventX = change.position.x
                }
            }
    ) {
        val textSizePx = 10.sp.toPx()
        val paint = Paint(Paint.ANTI_ALIAS_FLAG)
        paint.textSize = textSizePx
        drawIntoCanvas { canvas ->
            if (max == Double.MIN_VALUE && min == Double.MAX_VALUE) {
                return@Canvas
            }

            val titleHeight = if (style.showTitle) {
                18.dp.toPx()
            } else {
                0f
            }
            val timelineHeight = 18.dp.toPx()

            val maxText = "%.2f%%".format(max)
            val leftTextWidth = paint.measureText(maxText)
            val timeTextWidth = paint.measureText(timeText)
            val lineChartWidth = size.width - leftTextWidth - timeTextWidth * 0.5f - 4.dp.toPx()
            val lineChartHeight = size.height - timelineHeight - titleHeight
            // 图表起始位置
            val lineChartStart = leftTextWidth + 4.dp.toPx()

            // 每个格子宽度
            val pointWidth = lineChartWidth / data.totalCount.dec()

            // 绘制标题
            drawTitle(paint, titleHeight, data, canvas, style)

            // 左侧文字
            drawLeftText(
                canvas,
                paint,
                leftTextWidth,
                titleHeight,
                lineChartHeight,
                maxText,
                textSizePx,
                max
            )

            // 底部时间轴文字
            drawTimelineText(
                canvas,
                paint,
                data,
                style,
                lineChartStart,
                lineChartHeight,
                titleHeight,
                textSizePx,
                pointWidth
            )

            // 绘制坐标轴
            drawAxis(
                canvas,
                paint,
                data.totalCount,
                lineChartStart,
                lineChartHeight,
                titleHeight,
                pointWidth
            )
            // 绘制虚线
            drawDashedLine(
                canvas,
                paint,
                data.totalCount,
                lineChartStart,
                lineChartHeight,
                titleHeight,
                pointWidth
            )
            // 绘制折线
            drawLineChart(
                canvas,
                paint,
                data.lines,
                style,
                lineChartStart,
                lineChartHeight,
                titleHeight,
                max,
                min,
                pointWidth
            )

            // 松开后不显示弹框
            if (eventX == -1f) return@Canvas

            drawEventPopup(
                eventX,
                lineChartStart,
                pointWidth,
                data,
                paint,
                canvas,
                titleHeight,
                lineChartHeight,
                style,
                max,
                min
            )

            // 重置画笔
            paint.style = Paint.Style.FILL
            paint.strokeWidth = 0f
        }
    }

}

private fun <T : ILine> DrawScope.drawTitle(
    paint: Paint,
    titleHeight: Float,
    data: LineChartData<T>,
    canvas: Canvas,
    style: LineChartStyle
) {
    paint.textSize = 12.sp.toPx()
    if (titleHeight != 0f) {
        var beforeX = size.width - 10.dp.toPx()
        for ((index, line) in data.lines.reversed().withIndex()) {
            val titleWidth = paint.measureText(line.first)
            val x = beforeX - titleWidth
            val y = paint.textSize * 0.5f
            paint.color = CaptionTextColor.toNativeColor()
            paint.style = Paint.Style.FILL
            canvas.nativeCanvas.drawText(
                line.first,
                x,
                y,
                paint
            )
            paint.style = Paint.Style.STROKE
            paint.strokeWidth = 1.5.dp.toPx()
            paint.color = style.lineColors[style.lineColors.size.dec() - index]
            canvas.nativeCanvas.drawCircle(
                x - 8.dp.toPx(),
                y - 5.dp.toPx(),
                5.dp.toPx(),
                paint
            )
            beforeX = x - 30.dp.toPx()
        }
        paint.style = Paint.Style.FILL
    }
}

private fun <T : ILine> DrawScope.drawEventPopup(
    eventX: Float,
    lineChartStart: Float,
    pointWidth: Float,
    data: LineChartData<T>,
    paint: Paint,
    canvas: Canvas,
    titleHeight: Float,
    lineChartHeight: Float,
    style: LineChartStyle,
    max: Double,
    min: Double
) {
    // 计算选中位置
    val selectX = when {
        eventX <= lineChartStart -> {
            lineChartStart
        }
        eventX >= lineChartStart + pointWidth * data.lines[0].second.size.dec() -> {
            lineChartStart + pointWidth * data.lines[0].second.size.dec()
        }
        else -> {
            eventX
        }
    }
    val index = ((selectX - lineChartStart) / pointWidth).roundToInt()
    val selectedIndex = if (index.inc() < data.lines[0].second.size) {
        index
    } else {
        data.lines[0].second.size.dec()
    }

    // 绘制选中垂直虚线
    paint.style = Paint.Style.STROKE
    paint.strokeWidth = 0.5.dp.toPx()
    paint.color = DashedLineColor.toNativeColor()
    paint.pathEffect = DashPathEffect(floatArrayOf(3.dp.toPx(), 3.dp.toPx()), 0f)

    canvas.nativeCanvas.drawLine(
        pointWidth * selectedIndex + lineChartStart,
        titleHeight,
        pointWidth * selectedIndex + lineChartStart,
        titleHeight + lineChartHeight,
        paint
    )

    // 绘制线上圆圈
    paint.pathEffect = null
    paint.style = Paint.Style.FILL
    for ((lineIndex, line) in data.lines.withIndex()) {
        paint.color = style.lineColors[lineIndex]

        // 选中的数据
        val selectedData = line.second[selectedIndex]
        val selectY = getY(lineChartHeight, max, min, selectedData.value()) + titleHeight
        // 线上外圆
        canvas.nativeCanvas.drawCircle(
            pointWidth * selectedIndex + lineChartStart,
            selectY,
            5.dp.toPx(),
            paint
        )
        // 线上内圆
        paint.color = Color.White.toNativeColor()
        canvas.nativeCanvas.drawCircle(
            pointWidth * selectedIndex + lineChartStart,
            selectY,
            3.5.dp.toPx(),
            paint
        )
    }

    paint.textSize = 11.sp.toPx()
    val popupWidth = 100.dp.toPx()
    val popupHeight = 30.dp.toPx() + data.lines.size * 1.5f * paint.textSize
//            //  - 2.5.dp.toPx() = 减去圆圈半径的一半
    val popupX = pointWidth * index - 2.5.dp.toPx()
    val selectedMaxValue = data.lines.maxOf { it.second[selectedIndex].value() }
    val selectY = getY(lineChartHeight, max, min, selectedMaxValue) + titleHeight
    val popupY = selectY - popupHeight - 10.dp.toPx()

    paint.color = PrimaryColor.toNativeColor()
    paint.style = Paint.Style.FILL

    // 绘制选择弹框
    paint.color = Color(0x99000000).toNativeColor()
    canvas.nativeCanvas.drawRoundRect(
        popupX,
        popupY,
        popupX + popupWidth,
        popupY + popupHeight,
        2.dp.toPx(),
        2.dp.toPx(),
        paint
    )
    paint.style = Paint.Style.FILL
    paint.color = Color.White.toNativeColor()
    // 日期
    canvas.nativeCanvas.drawText(
        data.lines[0].second[selectedIndex].dateTime(),
        popupX + 6.dp.toPx(),
        popupY + paint.textSize + 8.dp.toPx(),
        paint
    )

    // 绘制圆点和文本
    for (i in 0 until data.lines.size) {
        paint.color = style.lineColors[i]
        canvas.nativeCanvas.drawCircle(
            popupX + 8.dp.toPx(),
            popupY + paint.textSize + 8.dp.toPx() + (paint.textSize + 4.dp.toPx()) * (i + 1),
            2.5.dp.toPx(),
            paint
        )
        paint.color = Color.White.toNativeColor()
        canvas.nativeCanvas.drawText(
            "%s:%.2f%%".format(
                data.lines[i].first,
                data.lines[i].second[selectedIndex].value()
            ),
            popupX + 15.dp.toPx(),
            popupY + paint.textSize + 12.dp.toPx() + (paint.textSize + 4.dp.toPx()) * (i + 1),
            paint
        )
    }
}


/**
 * 通过数值获取Y坐标（转换Y坐标值）
 *
 * @param containerHeight 容器高度
 * @param max 最大max值
 * @param min 最小min值
 * @param value 需要转换为Y坐标的数值
 *
 * @author Jero
 */
private fun getY(containerHeight: Float, max: Double, min: Double, value: Double): Float {
    val scaleY = containerHeight / (max - min)
    return ((max - value) * scaleY).toFloat()
}

private fun <T : ILine> DrawScope.drawLineChart(
    canvas: Canvas,
    paint: Paint,
    data: List<Line<T>>,
    style: LineChartStyle,
    lineChartStart: Float,
    lineChartHeight: Float,
    titleHeight: Float,
    max: Double,
    min: Double,
    pointWidth: Float
) {
    paint.pathEffect = null
    paint.style = Paint.Style.STROKE
    paint.strokeWidth = 1.dp.toPx()
    val scaleY = lineChartHeight / (max - min)

    for ((outerIndex, line) in data.withIndex()) {
        val path = Path()
        paint.color = style.lineColors[outerIndex]
        for ((index, datum) in line.second.withIndex()) {
            val curX = lineChartStart + pointWidth * index
            val preX = if (index == 0) {
                curX
            } else {
                lineChartStart + pointWidth * (index - 1)
            }
            val curY = getY(lineChartHeight, max, min, datum.value()) + titleHeight
            val preY = if (index == 0) {
                curY
            } else {
                ((max - line.second[index - 1].value()) * scaleY + titleHeight).toFloat()
            }
            if (path.isEmpty) {
                path.moveTo(preX, preY)
            }
            val wt = (preX + curX) / 2
            path.cubicTo(wt, preY, wt, curY, curX, curY)
        }
        canvas.nativeCanvas.drawPath(path, paint)
    }
}

private fun DrawScope.drawDashedLine(
    canvas: Canvas,
    paint: Paint,
    totalCount: Int,
    lineChartStart: Float,
    lineChartHeight: Float,
    titleHeight: Float,
    pointWidth: Float
) {
    paint.strokeWidth = 0.6.dp.toPx()
    paint.color = DashedLineColor.toNativeColor()
    paint.pathEffect = DashPathEffect(floatArrayOf(3.dp.toPx(), 3.dp.toPx()), 0f)
    for (i in 0 until 4) {
        canvas.nativeCanvas.drawLine(
            lineChartStart,
            lineChartHeight * 0.25f * i + titleHeight,
            lineChartStart + pointWidth * totalCount.dec(),
            lineChartHeight * 0.25f * i + titleHeight,
            paint
        )
    }
}

private fun DrawScope.drawAxis(
    canvas: Canvas,
    paint: Paint,
    totalCount: Int,
    lineChartStart: Float,
    lineChartHeight: Float,
    titleHeight: Float,
    pointWidth: Float
) {
    paint.color = AxisLineColor.toNativeColor()
    paint.style = Paint.Style.STROKE
    paint.strokeWidth = 0.5.dp.toPx()
    // yAxis
    canvas.nativeCanvas.drawLine(
        lineChartStart,
        titleHeight,
        lineChartStart,
        lineChartHeight + titleHeight,
        paint
    )
    // xAxis
    canvas.nativeCanvas.drawLine(
        lineChartStart,
        lineChartHeight + titleHeight,
        lineChartStart + pointWidth * totalCount.dec(),
        lineChartHeight + titleHeight,
        paint
    )
}

private fun <T : ILine> DrawScope.drawTimelineText(
    canvas: Canvas,
    paint: Paint,
    data: LineChartData<T>,
    style: LineChartStyle,
    lineChartStart: Float,
    lineChartHeight: Float,
    titleHeight: Float,
    textSizePx: Float,
    pointWidth: Float
) {
    val timelines = data.lines[0].second

    val timeTextWidth =
        paint.measureText("9999.99.99")
    paint.color = style.timelineTextColor
    paint.textSize = textSizePx

    val timelineCount = data.timelineCount
    for (i in 0 until timelineCount) {
        val index = if (i.inc() == timelineCount) {
            // 最后一条
            timelines.size.dec()
        } else {
            i * (timelines.size / timelineCount.dec())
        }
        timelines.getOrNull(index)?.apply {
            canvas.nativeCanvas.drawText(
                this.dateTime(),
                lineChartStart - (timeTextWidth * 0.5f) + pointWidth * index,
                lineChartHeight + textSizePx + 4.dp.toPx() + titleHeight,
                paint
            )
        }
    }
}

private fun DrawScope.drawLeftText(
    canvas: Canvas,
    paint: Paint,
    leftTextWidth: Float,
    lineChartTop: Float,
    lineChartHeight: Float,
    maxText: String,
    textSizePx: Float,
    max: Double,
) {
    paint.color = RedColor.toNativeColor()
    paint.textSize = textSizePx
    paint.textAlign = Paint.Align.RIGHT
    val textOffsetY = textSizePx * 0.4f
    canvas.nativeCanvas.drawText(
        maxText,
        leftTextWidth,
        textOffsetY + lineChartTop,
        paint
    )
    val halfMaxText = "%.2f%%".format(max * 0.5f)
    canvas.nativeCanvas.drawText(
        halfMaxText,
        leftTextWidth,
        lineChartHeight * 0.25f + textOffsetY + lineChartTop,
        paint
    )

    paint.color = CaptionTextColor.toNativeColor()
    val zeroText = "%.2f%%".format(0f)
    canvas.nativeCanvas.drawText(
        zeroText,
        leftTextWidth,
        lineChartHeight * 0.5f + textOffsetY + lineChartTop,
        paint
    )

    paint.color = GreenColor.toNativeColor()
    val minusHalfMinText = "%.2f%%".format(-max * 0.5f)
    canvas.nativeCanvas.drawText(
        minusHalfMinText,
        leftTextWidth,
        lineChartHeight * 0.75f + textOffsetY + lineChartTop,
        paint
    )
    val minusMinText = "%.2f%%".format(-max)
    canvas.nativeCanvas.drawText(
        minusMinText,
        leftTextWidth,
        lineChartHeight + textOffsetY + lineChartTop,
        paint
    )
    paint.textAlign = Paint.Align.LEFT
}